//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Resource Application Block
//
// Original Author: Guy Smith-Ferrier
// Date Created   : September 2005
// Notes          : This file is part of the downloadable source code for .NET Internationalization, by
//                  Guy Smith-Ferrier, published by Addison-Wesley. See http://www.dotneti18n.com for details.
// Disclaimer     : No warranty is provided. Use at your own risk.
// Modification   : You are free to modify this code providing that this block of comments is not altered
//                  and is placed before any code.
//===============================================================================
// Edit Author		: Steve Phillips
// Date Edited	  : February 2008
// Notes          : This file has been very much modified from the original to integrate with the Resource Application Block
//									and to include both C# and VB code generation.
//===============================================================================

using System;
using System.Diagnostics.CodeAnalysis;
using System.Security.Permissions;
using System.Globalization;
using System.IO;
using System.Collections;
using System.Resources.Tools;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using Microsoft.VisualBasic;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using EntLibContrib.Resource.Managers;
using EntLibContrib.Resource.Properties;

namespace EntLibContrib.Resource.Configuration.Design.Generators
{
  /// <summary>
  /// StronglyTypedResourceBuilderHelper is a helper class for StronglyTypedResourceBuilder
  /// </summary>
  /// <remarks>Use StronglyTypedResourceBuilderHelper.GenerateClass to generate a strongly typed resource
  /// class which uses the Resource Application Block instead of the ResourceManager class</remarks>
  public static class StronglyTypedResourceBuilderHelper
  {
		#region Fields
		private static string internalInstance = "InternalResourceFactory";
		private static string originalCodeMemberFieldName = "resourceMan";
		private static string codeMemberFieldName = "resourceManager";
		private static string codeMemberPropertyName = "ResourceManager";
		private static string codeMemberExternalTypeReference = "EntLibContrib.Resource.ResourceFactory";
		private static string codeMemberInternalTypeReference = "EntLibContrib.Resource.InternalResourceFactory";
		private static string codeMemberMethodName = "GetResourceManager";
		private static CodeTypeReference resourceManagerTypeReference = new CodeTypeReference(typeof(IResourceManager), CodeTypeReferenceOptions.GlobalReference);
		#endregion

		#region Public Static Methods
		/// <summary>
		/// GenerateClass generates a strongly typed C# or VB class from a resource
		/// </summary>
		/// <param name="configurationFile">The configuration file.</param>
		/// <param name="resourceInstance">The resource instance.</param>
		/// <param name="namespaceName">Name of the namespace.</param>
		/// <param name="className">The name of the generated class</param>
		/// <param name="classFileName">Name of the class file.</param>
		/// <remarks>The programming language used is dependant on the file extension</remarks>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2116:AptcaMethodsShouldOnlyCallAptcaMethods"), SuppressMessage("Microsoft.Performance", "CA1807:AvoidUnnecessaryStringCreation")]
		public static void GenerateClass(string configurationFile, string resourceInstance, string namespaceName, string className, string classFileName)
    {
			// validate parameters
			if (String.IsNullOrEmpty(configurationFile))
				throw new ArgumentNullException("configurationFile");

			if (String.IsNullOrEmpty(classFileName))
				throw new ArgumentNullException("classFileName");

			if (String.IsNullOrEmpty(namespaceName))
				namespaceName = InternalResources.DefaultResourceNamespace;

			if (String.IsNullOrEmpty(className))
				className = Path.GetFileNameWithoutExtension(classFileName);

			// decide on the code provider based on the extension
      CodeDomProvider codeDomProvider;
      switch (Path.GetExtension(classFileName).ToLower(CultureInfo.CurrentCulture))
      {
				case ".cs" :
					codeDomProvider = new CSharpCodeProvider();
					break;
				case ".vb" :
					codeDomProvider = new VBCodeProvider();
					break;
				default :
					throw new ArgumentOutOfRangeException("classFileName", InternalResources.ExceptionInvalidExtension);
			}

			// get the collection of resources
			string[] unmatchable;
			Hashtable resources = GetResources(configurationFile, resourceInstance);

			// generate the code
			CodeCompileUnit codeCompileUnit = StronglyTypedResourceBuilder.Create(resources, className, namespaceName, codeDomProvider, false, out unmatchable);

			// change the resource manager to use the Resource Application Block
			ChangeResourceManager(resourceInstance, codeCompileUnit);

			// write out the code to file
			new SecurityPermission(PermissionState.Unrestricted).Demand();
			using (TextWriter writer = new StreamWriter(classFileName))
			{
				codeDomProvider.GenerateCodeFromCompileUnit(codeCompileUnit, writer, new CodeGeneratorOptions());
			}
		}
    #endregion

		#region Private Static Methods
		private static Hashtable GetResources(string configurationFile, string resourceInstance)
    {
			// create a configuration source from the configuration file
			FileConfigurationSource.ResetImplementation(configurationFile, false);
			FileConfigurationSource configurationSource = new FileConfigurationSource(configurationFile);

			// create a resource manager factory
			ResourceManagerFactory factory = new ResourceManagerFactory(configurationSource);

			// use the instance name to get a resource manager
			IResourceManager manager;
			if (String.IsNullOrEmpty(resourceInstance))
				manager = factory.CreateDefault();
			else
				manager = factory.Create(resourceInstance);

			// get the resource set for the invariant culture
			CommonResourceSet resourceSet = (CommonResourceSet)manager.GetResourceSet(CultureInfo.InvariantCulture);

			// return the resources
			return resourceSet.Resources;
    }

		private static void ChangeResourceManager(string resourceInstance, CodeCompileUnit codeCompileUnit)
    {
      CodeTypeDeclaration codeTypeDeclaration = codeCompileUnit.Namespaces[0].Types[0];

			// change the resource manager field declaration
			CodeMemberField codeMemberField = GetCodeMemberField(codeTypeDeclaration, originalCodeMemberFieldName);
			if (codeMemberField != null)
				ChangeResourceManagerFieldDeclaration(codeMemberField);

			// change the resource manager property get statements
			CodeMemberProperty codeMemberProperty = GetCodeMemberProperty(codeTypeDeclaration, codeMemberPropertyName);
			if (codeMemberProperty != null)
				ChangeResourceManagerGetStatements(resourceInstance, codeMemberProperty);
		}

		private static void ChangeResourceManagerFieldDeclaration(CodeMemberField codeMemberField)
		{
			// change the declaration from:-
			// private static global::System.Resources.ResourceManager resourceMan;
			//
			// to:-
			// private static global::EntLibContrib.Resource.IResourceManager resourceManager;
			//
			codeMemberField.Name = codeMemberFieldName;
			codeMemberField.Type = resourceManagerTypeReference;
		}

		private static void ChangeResourceManagerGetStatements(string resourceInstance, CodeMemberProperty codeMemberProperty)
		{
			// change the statements from:-
			// if ((resourceMan == null))
			// {
			//     System.Resources.ResourceManager temp = new System.Resources.ResourceManager("WindowsApplication2.strings", typeof(strings).Assembly);
			//     resourceMan = temp;
			// }
			// return resourceManager;
			//
			// to:-
			//
			// if ((resourceManager == null))
			// {
			//     resourceManager = EntLibContrib.Resource.ResourceFactory.GetResourceManager("instance name");
			// }
			CodeFieldReferenceExpression resourceManagerFieldReferenceExpression = new CodeFieldReferenceExpression(null, codeMemberFieldName);

			// build the condition to check if the resource manager is null
			CodeExpression ifExpression = new CodeBinaryOperatorExpression(resourceManagerFieldReferenceExpression, CodeBinaryOperatorType.IdentityEquality, new CodePrimitiveExpression(null));

			// build the resource factory parameter list
			CodeExpression[] resourceManagerParameterExpressions = new CodeExpression[1]{new CodePrimitiveExpression(resourceInstance)};

			// build the resource factory method call to GetResourceManager
			CodeExpression newResourceManagerExpression = null;
			if (resourceInstance == internalInstance)
				newResourceManagerExpression = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(codeMemberInternalTypeReference), codeMemberMethodName);
			else
				newResourceManagerExpression = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(codeMemberExternalTypeReference), codeMemberMethodName, resourceManagerParameterExpressions);

			// build the condition statements
			CodeStatement[] ifStatements = new CodeStatement[1] { new CodeAssignStatement(resourceManagerFieldReferenceExpression, newResourceManagerExpression) };

			// build the get statements collection
			CodeStatementCollection statements = new CodeStatementCollection();

			// condition statements
			statements.Add(new CodeConditionStatement(ifExpression, ifStatements));

			// return statement
			statements.Add(new CodeMethodReturnStatement(resourceManagerFieldReferenceExpression));

			// now that we have collected all of the code, assign it to the property's get statements
			codeMemberProperty.Type = resourceManagerTypeReference;
			codeMemberProperty.GetStatements.Clear();
			codeMemberProperty.GetStatements.AddRange(statements);
		}

		private static CodeMemberField GetCodeMemberField(CodeTypeDeclaration codeTypeDeclaration, string fieldName)
		{
			foreach (CodeTypeMember codeTypeMember in codeTypeDeclaration.Members)
			{
				CodeMemberField codeMemberField = codeTypeMember as CodeMemberField;
				if (codeMemberField != null)
				{
					if (codeMemberField.Name == fieldName)
						return codeMemberField;
				}
			}
			return null;
		}

		private static CodeMemberProperty GetCodeMemberProperty(CodeTypeDeclaration codeTypeDeclaration, string propertyName)
		{
			foreach (CodeTypeMember codeTypeMember in codeTypeDeclaration.Members)
			{
				CodeMemberProperty codeMemberProperty = codeTypeMember as CodeMemberProperty;
				if (codeMemberProperty != null)
				{
					if (codeMemberProperty.Name == propertyName)
						return codeMemberProperty;
				}
			}
			return null;
		}
		#endregion
	}
}
